package org.codefinger.dao.util.magic;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.codefinger.dao.util.AbstractCache.ValueBuilder;
import org.codefinger.dao.util.ArrayIterator;
import org.codefinger.dao.util.IdentityCache;
import org.codefinger.dao.util.Lang;

public class MagicPojo<T> {

	private static final IdentityCache<Class<?>, MagicPojo<?>>	MAGIC_POJO_MAP	= new IdentityCache<Class<?>, MagicPojo<?>>(new MagicPojoBuilder());

	private Class<T>											clazz;

	private MagicConstructor<T>									constructor;

	private Map<String, MagicAttribute>							attributeMap;

	private Map<String, MagicAttribute>							upperAttributeMap;

	private MagicAttribute[]									magicAttributes;

	private MagicPojo(Class<T> clazz, MagicConstructor<T> constructor, Map<String, MagicAttribute> attributeMap, Map<String, MagicAttribute> upperAttributeMap, MagicAttribute[] magicAttributes) {
		super();
		this.clazz = clazz;
		this.constructor = constructor;
		this.attributeMap = attributeMap;
		this.upperAttributeMap = upperAttributeMap;
		this.magicAttributes = magicAttributes;
	}

	public T newInstance() {
		return constructor.newInstance();
	}

	public MagicAttribute getMagicAttribute(String name) {
		return attributeMap.get(name);
	}

	public MagicAttribute getMagicAttributeByUpperCase(String name) {
		return upperAttributeMap.get(name);
	}

	public Class<T> getClazz() {
		return clazz;
	}

	public MagicConstructor<T> getConstructor() {
		return constructor;
	}

	public Iterator<MagicAttribute> getMagicAttributes() {
		return new ArrayIterator<MagicAttribute>(magicAttributes);
	}

	public int attributesSize() {
		return magicAttributes.length;
	}

	@SuppressWarnings({"unchecked" })
	public static <T> MagicPojo<T> getMagicPojo(Class<T> clazz) {
		return (MagicPojo<T>) MAGIC_POJO_MAP.get(clazz);
	}

	public static class MagicPojoBuilder implements ValueBuilder<Class<?>, MagicPojo<?>> {

		@SuppressWarnings({ "unchecked", "rawtypes" })
		@Override
		public MagicPojo<?> build(Class<?> clazz) {
			MagicConstructor<?> constructor = MagicConstructor.getMagicConstructor(clazz);
			Map<String, MagicAttribute> attributeMap = new HashMap<String, MagicAttribute>();
			Map<String, MagicAttribute> upperAttributeMap = new HashMap<String, MagicAttribute>();
			List<MagicAttribute> attributes = new ArrayList<MagicAttribute>();

			Class<?> tmpClazz = clazz;
			while (tmpClazz != Object.class) {
				ArrayIterator<Method> arrayIteractor = new ArrayIterator<Method>(tmpClazz.getDeclaredMethods());

				for (Field field : tmpClazz.getDeclaredFields()) {

					String fieldName = field.getName();
					MagicAttribute magicAttribute = null;
					int modifiers = field.getModifiers();

					if (Modifier.isPublic(modifiers)) {
						magicAttribute = new PublicFieldMagicAttribute(field);
					} else {
						String getterName = Lang.joinString("get", fieldName);
						String setterName = Lang.joinString("set", fieldName);
						Class<?> type = field.getType();
						Class<?>[] params;
						MagicGetter magicGetter = null;
						MagicSetter magicSetter = null;
						Method method;

						while (arrayIteractor.hasNext()) {
							method = arrayIteractor.next();

							if (!Modifier.isPublic(method.getModifiers())) {
								continue;
							}

							params = method.getParameterTypes();
							if (method.getReturnType().equals(type) && params.length == 0) {
								if (method.getName().equalsIgnoreCase(getterName)) {

									magicGetter = new MagicMethodGetter(MagicMethod.getMagicMethod(method));

								}
							} else if (method.getName().equalsIgnoreCase(setterName) && params.length == 1 && type.equals(params[0])) {

								magicSetter = new MagicMethodSetter(MagicMethod.getMagicMethod(method));

							}
						}
						if (magicGetter == null) {
							magicGetter = new FieldMagicGetter(field);
						}

						if (Modifier.isFinal(modifiers)) {
							magicSetter = new FinalFieldMagicSetter();
						} else if (magicSetter == null) {
							magicSetter = new FieldMagicSetter(field);
						}

						magicAttribute = new GetterSetterMagicAttribute(field, magicGetter, magicSetter);

						arrayIteractor.reset();
					}

					if (!attributeMap.containsKey(fieldName)) {
						attributeMap.put(fieldName, magicAttribute);
					}

					String upperFieldName = fieldName.toUpperCase();
					if (!upperAttributeMap.containsKey(upperFieldName)) {
						upperAttributeMap.put(upperFieldName, magicAttribute);
					}

					attributes.add(magicAttribute);
				}
				tmpClazz = tmpClazz.getSuperclass();
			}

			return new MagicPojo(clazz, constructor, attributeMap, upperAttributeMap, attributes.toArray(new MagicAttribute[attributes.size()]));
		}

	}
}
